Mobile — Examples
Complete working examples for mobile Aspects. Each example runs in a WebView as an .html document with a regular DOM (head/body). They demonstrate mobile-specific patterns (HTML wrapper, native bridges, window negotiation) and shared patterns such as optional Shadow DOM for style isolation when needed.
For background on the categories (context-less vs. context-aware), see the Aspects Overview. For a side-by-side comparison with web patterns, see the Mobile Technical Reference.
Context-less: Notification Banner
A simple mobile Aspect that displays a notification banner inside the WebView and reports its layout to the native app. No user context or authentication is needed.
Patterns demonstrated: HTML wrapper, meta tag injection, sizeAndLocation bridge.
<html>
<head></head>
<body>
<script type="text/javascript">
// Meta tags
var meta1 = document.createElement('meta');
meta1.setAttribute('charset', 'utf-8');
document.head.appendChild(meta1);
var meta2 = document.createElement('meta');
meta2.setAttribute('name', 'viewport');
meta2.setAttribute('content', 'width=device-width, initial-scale=1, shrink-to-fit=no');
document.head.appendChild(meta2);
// Create banner
var banner = document.createElement('div');
banner.id = 'notification-banner';
banner.textContent = 'Welcome! Check out our new savings rates.';
banner.style.cssText =
'background-color:#1a73e8;color:#ffffff;text-align:center;' +
'padding:12px 20px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;' +
'font-size:14px;position:fixed;top:0;left:0;width:100%;z-index:9999;';
document.body.appendChild(banner);
// Report layout to native app
var bannerRect = banner.getBoundingClientRect();
var aspectDetails = {
aspectLocations: [{
x: '0',
y: '0',
width: '' + window.screen.width,
height: '' + Math.ceil(bannerRect.height * 1.25),
webWidth: '' + window.screen.width,
webHeight: '' + window.screen.height
}]
};
var jsonString = JSON.stringify(aspectDetails);
if (navigator.userAgent && navigator.userAgent.match(/(iPad|iPhone|iPod)/i)) {
window.webkit.messageHandlers.sizeAndLocation.postMessage(jsonString);
} else {
JSBridge.sizeAndLocation(jsonString);
}
</script>
</body>
</html>
Context-aware: Third-Party Widget with Native Bridge Authentication
A full mobile Aspect that integrates a third-party vendor widget. This is the most common real-world pattern for mobile Aspects and demonstrates every mobile-specific concept.
Patterns demonstrated:
- HTML wrapper with meta tag injection
- Native bridge authentication (
tokenApiDetails→apiToken:ready) - Manual script loading (
document.createElement('script')) - Optional Shadow DOM for style isolation
- Window size negotiation (
condenseWindow/expandWindow) MutationObserverfor widget state trackingwaitForElementfor async element detection
<html>
<head></head>
<body>
<script type="text/javascript">
// ============== META TAGS ==============
var meta1 = document.createElement('meta');
meta1.setAttribute('charset', 'utf-8');
document.head.appendChild(meta1);
var meta2 = document.createElement('meta');
meta2.setAttribute('name', 'viewport');
meta2.setAttribute('content', 'width=device-width, initial-scale=1, shrink-to-fit=no');
document.head.appendChild(meta2);
// ============== VENDOR CONFIGURATION ==============
// Replace these values with your vendor's actual configuration
var VENDOR_CONFIG = {
scriptUrl: 'https://cdn.your-vendor.com/sdk/v3/widget.js',
containerId: 'vendor-widget',
launcherSelector: 'div.vendor-launcher',
visibilityAttr: 'data-visible'
};
// ============== WIDGET CONTAINER (Shadow DOM) ==============
var widgetDiv = document.createElement('div');
widgetDiv.id = VENDOR_CONFIG.containerId;
var shadowRoot = widgetDiv.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = '<div></div>';
document.body.appendChild(widgetDiv);
// ============== STYLES ==============
var css = '#' + VENDOR_CONFIG.containerId +
' { z-index: 999999 !important; }';
var styleEl = document.createElement('style');
styleEl.appendChild(document.createTextNode(css));
document.head.appendChild(styleEl);
// ============== NATIVE BRIDGE: AUTHENTICATION ==============
// Send API call details to the native app for execution
var apiCallDetails = {
method: 'GET',
headers: { cookie: '' },
url: 'https://<FI_DOMAIN>/feng-bff/live/v1/aspect/token?clientId=<YOUR_CLIENT_ID>'
};
var apiCallString = JSON.stringify(apiCallDetails);
if (navigator.userAgent && navigator.userAgent.match(/(iPad|iPhone|iPod)/i)) {
window.webkit.messageHandlers.tokenApiDetails.postMessage(apiCallString);
} else {
JSBridge.tokenApiDetails(apiCallString);
}
// Wait for the native app to deliver the auth token
function getAuthToken() {
if (window.dbk && window.dbk.tokenResponse) {
return Promise.resolve(window.dbk.tokenResponse);
}
return new Promise(function (resolve) {
var onReady = function () {
window.removeEventListener('apiToken:ready', onReady);
resolve(window.dbk.tokenResponse);
};
window.addEventListener('apiToken:ready', onReady);
});
}
// ============== NATIVE BRIDGE: WINDOW SIZE ==============
var chatButton;
var chatButtonLocation;
function resizeWindow(aspectDetails) {
var jsonString = JSON.stringify(aspectDetails);
if (navigator.userAgent && navigator.userAgent.match(/(iPad|iPhone|iPod)/i)) {
window.webkit.messageHandlers.sizeAndLocation.postMessage(jsonString);
} else {
JSBridge.sizeAndLocation(jsonString);
}
}
function condenseWindow() {
try {
var aspectDetails = {
aspectLocations: [{
x: '' + chatButtonLocation.x,
y: '' + chatButtonLocation.y,
width: '' + (chatButtonLocation.width * 1.25),
height: '' + (chatButtonLocation.height * 1.25),
webWidth: '' + window.screen.width,
webHeight: '' + window.screen.height
}]
};
resizeWindow(aspectDetails);
} catch (ex) {
console.error('Condense window error:', ex);
}
}
function expandWindow() {
try {
var aspectDetails = {
aspectLocations: [{
x: '0',
y: '0',
width: '' + window.screen.width,
height: '' + window.screen.height,
webWidth: '' + window.screen.width,
webHeight: '' + window.screen.height
}]
};
resizeWindow(aspectDetails);
} catch (ex) {
console.error('Expand window error:', ex);
}
}
// ============== ASYNC ELEMENT DETECTION ==============
function waitForElement(root, selector, timeout) {
timeout = timeout || 10000;
return new Promise(function (resolve, reject) {
var element = root.querySelector(selector);
if (element) { resolve(element); return; }
var observer = new MutationObserver(function (mutations) {
for (var i = 0; i < mutations.length; i++) {
var added = mutations[i].addedNodes;
for (var j = 0; j < added.length; j++) {
if (added[j].nodeType === Node.ELEMENT_NODE) {
var found = root.querySelector(selector);
if (found) {
observer.disconnect();
clearTimeout(timeoutId);
resolve(found);
return;
}
}
}
}
});
observer.observe(root, { childList: true, subtree: true });
var timeoutId = setTimeout(function () {
observer.disconnect();
reject(new Error('Element not found: ' + selector));
}, timeout);
});
}
// ============== WIDGET STATE OBSERVATION ==============
function setUp(launcherElement) {
chatButton = launcherElement;
chatButtonLocation = chatButton.getBoundingClientRect();
// Find the vendor's root element inside Shadow DOM
var vendorRoot = document.querySelector('#' + VENDOR_CONFIG.containerId)
.shadowRoot.querySelector('div.vendor-root');
// Observe open/close state changes
var stateObserver = new MutationObserver(function (mutationsList) {
for (var i = 0; i < mutationsList.length; i++) {
var mutation = mutationsList[i];
if (mutation.type === 'attributes') {
var isVisible = mutation.target.getAttribute(
mutation.attributeName) === 'true';
if (isVisible) {
expandWindow();
} else {
condenseWindow();
}
}
}
});
stateObserver.observe(vendorRoot, {
attributes: true,
attributeFilter: [VENDOR_CONFIG.visibilityAttr],
attributeOldValue: true
});
// Start in condensed state
condenseWindow();
}
// ============== SCRIPT LOADING AND INITIALIZATION ==============
var script = document.createElement('script');
script.src = VENDOR_CONFIG.scriptUrl;
script.type = 'text/javascript';
script.onload = function () {
try {
// Initialize vendor SDK (replace with your vendor's API)
var widget = new VendorSDK({
authenticate: getAuthToken
});
} catch (error) {
console.error('Failed to initialize vendor SDK:', error);
}
};
script.onerror = function () {
console.error('Failed to load vendor script');
};
document.body.appendChild(script);
// Wait for the vendor launcher to appear, then set up state observation
var hostElement = document.querySelector('#' + VENDOR_CONFIG.containerId);
waitForElement(hostElement.shadowRoot, VENDOR_CONFIG.launcherSelector)
.then(function (launcherElement) {
setUp(launcherElement);
})
.catch(function (error) {
console.error('Error:', error);
});
</script>
</body>
</html>
Replace VENDOR_CONFIG values with your actual vendor's script URL. The VendorSDK constructor, launcherSelector, and visibilityAttr must match your specific vendor's JavaScript SDK. Check the vendor's documentation for the correct selectors and state attributes.
Side-by-Side: Web vs. Mobile for the Same Integration
The table below shows how each step of a third-party widget integration maps between platforms. Use this when you need to build both a web and mobile Aspect for the same vendor.
| Step | Web Aspect | Mobile Aspect |
|---|---|---|
| File format | .js file | .html document with <script> block |
| Platform guard | isRunningInWebView() check at top — throws if in WebView | No guard needed |
| Meta tags | Not needed (host page provides them) | Inject programmatically at start of script |
| Request auth | fetch(tokenUrl, { Cookie: document.cookie }) | tokenApiDetails bridge → native app executes |
| Receive auth | fetch() Promise resolves with response JSON | Listen for apiToken:ready event on window |
| Load vendor script | dbk.loadScript(url) | document.createElement('script') with onload |
| Layout | CSS handles positioning | Report bounding box via sizeAndLocation bridge |
| Detect open/close | Not needed (CSS transitions handle it) | MutationObserver on vendor's visibility attribute |
| On open | No action needed | Call expandWindow() → full-screen WebView |
| On close | No action needed | Call condenseWindow() → button-sized WebView |
| Wait for elements | Not needed (direct DOM access) | waitForElement() utility with MutationObserver |
Next Steps
- Mobile Technical Reference — Platform APIs, native bridges, and security
- Web Examples — See how the same patterns are simpler on web